#include "HalideRuntime.h"
#include <cmath>
#include <stdio.h>
#include <stdlib.h>
#include <limits>

void h_assert(bool condition, const char *msg) {
    if (!condition) {
        printf("FAIL: %s\n", msg);
        abort();
    }
}

float float_from_bits(uint32_t bits) {
    union {
        float asFloat;
        uint32_t asUInt;
    } out;
    out.asUInt = bits;
    return out.asFloat;
}

double double_from_bits(uint64_t bits) {
    union {
        double asDouble;
        uint64_t asUInt;
    } out;
    out.asUInt = bits;
    return out.asDouble;
}

int main() {
    uint16_t inputs[] = {
      0x0000, // +ve zero
      0x8000, // -ve zero
      0x7c00, // +ve infinity
      0xfc00, // -ve infinity
      0x7e00, // quiet NaN
      0x7bff, // Largest +ve normal number
      0xfbff, // Smallest -ve normal number
      0x0001, // Smallest +ve subnormal number
      0x8001, // Largest -ve subnormal number
      0x0002, // 2nd smallest +ve subnormal number
      0x8002, // 2nd largest -ve subnormal number
      0x0003, // 3rd smallest subnormal number
      0x03ff, // Largest subnormal
      0x03fe, // 2nd largest subnormal
      0x3c00, // 1.0
      0xbc00  // -1.0
    };

    // Unfortunately we can't use the C99 hex float format
    // here because MSVC doesn't support them
    float expectedF[] = {
        0.0f,
        -0.0f,
        std::numeric_limits<float>::infinity(),
        -std::numeric_limits<float>::infinity(),
        std::numeric_limits<float>::quiet_NaN(),
        65504.0f,
        -65504.0f,
        (1.0f)/(1<<24),
        (-1.0f)/(1<<24),
        (1.0f)/(1<<23),  // 0x1.000000p-23
        (-1.0f)/(1<<23), // -0x1.000000p-23
        (1.5f)/(1<<23), // 0x1.800000p-23
        float_from_bits(0x387fc000), //0x1.ff8000p-15,
        float_from_bits(0x387f8000), // 0x1.ff0000p-15,
        1.0f,
        -1.0f
    };

    double expectedD[] = {
        0.0,
        -0.0,
        std::numeric_limits<double>::infinity(),
        -std::numeric_limits<double>::infinity(),
        std::numeric_limits<double>::quiet_NaN(),
        65504.0,
        -65504.0,
        (1.0)/(1<<24),
        (-1.0)/(1<<24),
        (1.0)/(1<<23), // 0x1.000000000000p-23
        (-1.0)/(1<<23), // -0x1.000000000000p-23
        (1.5)/(1<<23), // 0x1.800000000000p-23
        double_from_bits(0x3f0ff80000000000), // 0x1.ff8000000000p-15,
        double_from_bits(0x3f0ff00000000000), // 0x1.ff0000000000p-15,
        1.0,
        -1.0
    };

    h_assert(sizeof(inputs)/sizeof(uint16_t) == sizeof(expectedF)/sizeof(float),
             "size of half array doesn't match float array");
    h_assert(sizeof(inputs)/sizeof(uint16_t) == sizeof(expectedD)/sizeof(double),
             "size of half array doesn't match double array");

    for (unsigned int index=0; index < sizeof(inputs)/sizeof(uint16_t); ++index) {
        uint16_t in = inputs[index];
        union {
            float asFloat;
            uint32_t asUInt;
        } outF;
        outF.asFloat = halide_float16_bits_to_float(in);
        union {
            double asDouble;
            uint64_t asUInt;
        } outD;
        outD.asDouble = halide_float16_bits_to_double(in);

        union {
          float asFloat;
          uint32_t asUInt;
        } expectedFValue;
        expectedFValue.asFloat = expectedF[index];

        union {
          double asDouble;
          uint64_t asUInt;
        } expectedDValue;
        expectedDValue.asDouble = expectedD[index];

        // Compare bits because NaN in not comparable
        h_assert(outF.asUInt == expectedFValue.asUInt, "Failed to match on convert to float");
        h_assert(outD.asUInt == expectedDValue.asUInt, "Failed to match on convert to double");
    }
    printf("Success!\n");
    return 0;
}
